/********************************************************/
/*	Author:		gong_libin			*/
/*	Date:		2014_08_25			*/
/*	File:		GtDrRecv.cpp			*/
/********************************************************/

#include "GtDcpRadius.h"

static UINT sg_uiMsg;
static char* sg_pszTele;
static char* sg_pszArea;
static GtIpc::CGtMsg* sg_pCMsg;

int main(int argc, char* argv[])
{
	int iTotal = 1;
	int iCount = 0;
 	int iIndex = 0;
	int iReturn = 0;
	char* pszBind = NULL;
	GtNet::CGtRaw CRaw;
	GtNet::CGtDgram CDgram;
	GtNet::CGtNet* pCNet = NULL;
	GtCls::CGtThread* pCThread = NULL;

	struct option stOpt[] = {
		{"tele", 1, 0, 'e'},
		{"area", 1, 0, 'a'},
		{"bind", 1, 0, 'b'},
		{"thread", 1, 0, 't'},
		{"version", 0, 0, 'v'},
		{"help", 0, 0, 'h'},
		{0, 0, 0, 0},
	};

	while (GT_FAILURE != (iReturn = getopt_long(argc, argv, "e:a:b:t:vh", stOpt, &iIndex))) {
		switch (iReturn) {
			case 'b':
				pszBind = optarg;
				break;
			case 'e':
				sg_pszTele = optarg;
				break;
			case 'a':
				sg_pszArea = optarg;
				break;
			case 't':
				iTotal = atoi(optarg);
				break;
			case 'v':
				GT_PRINT("GtRadius Build on %s %s.\n", __DATE__, __TIME__);
				return GT_SUCCESS;
			case 'h':
				GT_PRINT("Usage: %s [options]\n", argv[0]);
				GT_PRINT("Valid options are:\n");
				GT_PRINT("[0] -e or --tele:       set tele id\n");
				GT_PRINT("[0] -a or --area:       set area id\n");
				GT_PRINT("[0] -b or --bind:       set bind inet|unix\n");
				GT_PRINT("[0] -t or --thread:     set thread total\n");
				GT_PRINT("[0] -v or --version:    show version.\n");
				GT_PRINT("[0] -h or --help:       show help.\n");
				GT_PRINT("GtRadius Build on %s %s.\n", __DATE__, __TIME__);
				return GT_SUCCESS;
			default:
				return GT_SUCCESS;
		}
	}

	#ifdef _GT_DEBUG_
		iTotal = 1;
	#else
		GtFork();
	#endif

	signal(SIGINT, GtDrQuit);
	signal(SIGSEGV, GtDrQuit);
	signal(SIGTERM, GtDrQuit);

	/* msg */
	GT_PRINT("Will CMsg.GtIpcCreate...\n");
	sg_pCMsg = new GtIpc::CGtMsg[GT_DD_MSG];
	GtIpcMsgKernel(GT_DD_MAX, GT_DD_MNB, GT_DD_MNI);
	for (iCount = 0; iCount < GT_DD_MSG; iCount ++) {
		if (GT_SUCCESS != (sg_pCMsg + iCount)->GtIpcCreate((sg_pCMsg + iCount)->GtIpcKey(GT_DD_PATH, GT_DD_KEY + iCount))) {
			GT_ERROR("Failed to GtIpcCreate\n");
			return GT_FAILURE;
		}
	}

	/* bind */
	if (0x2f != *pszBind) {
		pCNet = &CRaw;
		GT_PRINT("Will GtDrInet...\n");
		iReturn = GtDrInet(pszBind, pCNet);
	}
	else {
		pCNet = &CDgram;
		GT_PRINT("Will GtDrUnix...\n");
		iReturn = GtDrUnix(pszBind, pCNet);
	}

	/* thread */
	if (GT_SUCCESS == iReturn) {
		pCThread = new GtCls::CGtThread[iTotal];
		GT_PRINT("Will CThread.GtThreadCreate...\n");
		for (iCount = 0; iCount < iTotal; iCount ++) {
			(pCThread + iCount)->GtThreadAttrInit();
			(pCThread + iCount)->GtThreadRoutineSet(GtDrCore);
			(pCThread + iCount)->GtThreadCreate(pCNet);
		}
		for (iCount = 0; iCount < iTotal; iCount ++) {
			(pCThread + iCount)->GtThreadJoin(NULL);
		}
	}
	else {
		GT_ERROR("Failed to GtDrBind\n");
	}

	return GT_SUCCESS;
}

void GtDrQuit(int iSignal)
{
	GT_ERROR("GtDcpRadius-1.1.0 stopped by signal %d\n", iSignal);

	exit(GT_FAILURE);
}

int GtDrInet(char* pszBind, GtNet::CGtNet* pCNet)
{
	struct ifreq stIfreq;
	struct sockaddr_ll stAddr;
	GtNet::CGtRaw* pCRaw = (GtNet::CGtRaw*)pCNet;

	if (GT_SUCCESS == pCRaw->GtRawInitRecv(GT_PF_PACKET, GT_SOCK_DGRAM, htons(GT_ETH_P_ALL))) {
		strcpy(stIfreq.ifr_name, pszBind);
		if (GT_SUCCESS == ioctl(pCRaw->GtNetGetSocket(), SIOCGIFINDEX, &stIfreq)) {
			stAddr.sll_family = GT_PF_PACKET;
			stAddr.sll_ifindex = stIfreq.ifr_ifindex;
			stAddr.sll_protocol = htons(GT_ETH_P_ALL);
			if (GT_SUCCESS != pCRaw->GtNetBind(&stAddr, sizeof(stAddr))) {
				goto GtError;
			}
		}
		else {
			goto GtError;
		}
	}
	else {
		goto GtError;
	}

	return GT_SUCCESS;

GtError:
	GT_ERROR("%s\n", strerror(errno));
	pCRaw->GtNetClose();

	return GT_FAILURE;
}

int GtDrUnix(char* pszBind, GtNet::CGtNet* pCNet)
{
	unlink(pszBind);

	return pCNet->GtNetInitRecv(pszBind);
}

void GtDrUser(GTDRUSER_S* pstUser)
{
	GT_PRINT("User-Name: %s\n", pstUser->m_szUser);
	GT_PRINT("NAS-IP-Address: %s\n", pstUser->m_szNasIP);
	GT_PRINT("Framed-IP-Address: %s\n", pstUser->m_szUsrIP);
	GT_PRINT("Calling-Station-Id: %s\n", pstUser->m_szCalls);
	GT_PRINT("Acct-Status-Type: %s\n", pstUser->m_szStatus);
	GT_PRINT("Area: %s\n", sg_pszArea);
	GT_PRINT("Tele: %s\n", sg_pszTele);

	return;
}

void GtDrQueue(GTDRUSER_S* pstUser)
{
	GTDCPMSG_S stMsg = {0, {'\0'}};
	GtNet::CGtPro CPro;
	GtNet::GTPROKV_S stKv[] = {
		{GT_DD_USID, strlen(pstUser->m_szUser), (UCHAR*)pstUser->m_szUser},
		{GT_DD_USIP, strlen(pstUser->m_szUsrIP), (UCHAR*)pstUser->m_szUsrIP},
		{GT_DD_STAT, strlen(pstUser->m_szStatus), (UCHAR*)pstUser->m_szStatus},
		{GT_DD_AREA, strlen(sg_pszArea), (UCHAR*)sg_pszArea},
		{GT_DD_TELE, strlen(sg_pszTele), (UCHAR*)sg_pszTele},
	};
	GtNet::GTPROHEAD_S stHead = {
		GT_DD_FLAG,
		GT_PRO_0,
		GT_PRO_DATA,
		GT_PRO_0,
		GT_PRO_N,
		GT_PRO_0,
		GT_DD_RADS,
		GT_PRO_0,
		GT_PRO_0,
		GT_DEF_SUCC,
		sizeof(stKv) / sizeof(GtNet::GTPROKV_S),
		GtProKvOutput(stKv, stHead.m_usTotal, stMsg.m_uszMsg + GT_PRO_HEAD, GT_KBYTES4 - GT_PRO_HEAD),
	};

	CPro.GtProInduce(&stHead);
	CPro.GtProCreate(stMsg.m_uszMsg, GT_PRO_HEAD);

	stMsg.m_iLength = GT_PRO_HEAD + stHead.m_iLength;
	if (GT_SUCCESS == (sg_pCMsg + (__sync_add_and_fetch(&sg_uiMsg, 1) % GT_DD_MSG))->GtMsgPut((void*)&stMsg, sizeof(GTDCPMSG_S), GT_IPC_NOWAIT)) {
		#ifdef _GT_DEBUG_
			GT_DEBUG("GtMsgPut: %d\n", stMsg.m_iLength);
		#endif
	}
	else {
		GT_ERROR("Failed to GtMsgPut\n");
	}

	return;
}

void* GtDrCore(void* pNet)
{
	int iLength = 0;
	UINT uiOffset = 0;
	UINT uiRadius = 0;
	GTDRUSER_S stUser;
	GtNet::CGtPro CPro;
	GTDRATTR_S* pstAttr = NULL;
	GTDRTYPE_S* pstType = NULL;
	struct udphdr* pstUdp = NULL;
	UINT uiAttr = sizeof(GTDRATTR_S);
	UCHAR uszPacket[GT_KBYTES8] = { 0 };
	GtNet::CGtNet* pCNet = (GtNet::CGtNet*)pNet;

	struct ip* const c_pstIP = (struct ip*)uszPacket;

	#ifdef _GT_DEBUG_
		GtCls::CGtTime CTime;
	#endif

	while ((iLength = pCNet->GtNetRecv(uszPacket, GT_KBYTES8)) > GT_PRO_HEAD) {
		if (IPPROTO_UDP == c_pstIP->ip_p) {
			#ifdef _GT_DEBUG_
				CTime.GtTimeOn();
			#endif

			pstUdp = (struct udphdr*)(uszPacket + c_pstIP->ip_hl * 4);
			if (GT_DR_PORT == ntohs(pstUdp->uh_dport)) {
				#ifdef _GT_DEBUG_
					GT_PRINT("\n");
					GT_DEBUG("GtDrCallBack[%lu] start\n", pthread_self());
					GT_DEBUG("SrcAddr: %s, DstAddr: %s\n", inet_ntoa(c_pstIP->ip_src), inet_ntoa(c_pstIP->ip_dst));
					GT_DEBUG("Protocol: UDP, SrcPort: %d, DstPort: %d\n", ntohs(pstUdp->uh_sport), ntohs(pstUdp->uh_dport));
				#endif

				pstType = (GTDRTYPE_S*)((UCHAR*)pstUdp + sizeof(struct udphdr));
				#ifdef _GT_DEBUG_
					GT_PRINT("Code: %d\t", pstType->m_ucCode);
					GT_PRINT("Ident: %d\t", pstType->m_ucIdent);
					GT_PRINT("Length: %d\n", ntohs(pstType->m_usLength));
					GtPacketDisplay((UCHAR*)pstType->m_szAuthent, GT_BYTE16);
				#endif

				/* attr */
				uiOffset = sizeof(GTDRTYPE_S);
				uiRadius = ntohs(pstType->m_usLength);
				memset(&stUser, '\0', sizeof(GTDRUSER_S));
				while (uiRadius - uiOffset > uiAttr) {
					pstAttr = (GTDRATTR_S*)((UCHAR*)pstType + uiOffset);
					switch (pstAttr->m_ucType) {
						case GT_DR_UNAME:
							memcpy(stUser.m_szUser, (UCHAR*)pstAttr + uiAttr, pstAttr->m_ucLength - uiAttr);
							break;
						case GT_DR_NASIP:
							strncpy(stUser.m_szNasIP, inet_ntoa(*(struct in_addr*)((UCHAR*)pstAttr + uiAttr)), GT_BYTE16);
							break;
						case GT_DR_FRMIP:
							strncpy(stUser.m_szUsrIP, inet_ntoa(*(struct in_addr*)((UCHAR*)pstAttr + uiAttr)), GT_BYTE16);
							break;
						case GT_DR_CALLS:
							memcpy(stUser.m_szCalls, (UCHAR*)pstAttr + uiAttr, pstAttr->m_ucLength - uiAttr);
							break;
						case GT_DR_ASTTY:
							sprintf(stUser.m_szStatus, "%d", ntohl(*(ULONG*)((UCHAR*)pstAttr + uiAttr)));
							break;
						default:
							break;
					}
					uiOffset += pstAttr->m_ucLength;
				}
				#ifdef _GT_DEBUG_
					GtDrUser(&stUser);
				#endif

				/* msg */
				GtDrQueue(&stUser);

				#ifdef _GT_DEBUG_
					GT_DEBUG("GtDrCallBack[%lu] over Use time ", pthread_self());
					CTime.GtTimeOff();
					CTime.GtTimeDisplay();
				#endif
			}
		}
	}

	pthread_exit(NULL);
}
